🚀 [Feature]: Plan job decides version before build so tested artifact equals published artifact#342
Conversation
Super-linter summary
All files and directories linted successfully For more information, see the GitHub Actions workflow run Powered by Super-linter |
Super-linter summary
All files and directories linted successfully For more information, see the GitHub Actions workflow run Powered by Super-linter |
…tion (#1) Module version resolution is now available as a standalone action. Workflows can call it before building so the resolved version is stamped into the artifact at build time, making the bytes that are tested the bytes that ship. - Resolves PSModule/Process-PSModule#326 ## New: Standalone `Resolve-PSModuleVersion` action The action consumes the JSON `Settings` output from [`PSModule/Get-PSModuleSettings`](https://github.com/PSModule/Get-PSModuleSettings) and emits: | Output | Description | | --- | --- | | `Version` | `Major.Minor.Patch` portion of the resolved version. | | `Prerelease` | Prerelease tag, empty when not a prerelease. | | `FullVersion` | Full version string including `VersionPrefix` and prerelease tag. | | `ReleaseType` | `Release`, `Prerelease`, or `None` when no version bump label is present. | | `CreateRelease` | `true` when a release or prerelease should be created. | Typical usage in the Plan job: ```yaml - name: Resolve module version id: resolve uses: PSModule/Resolve-PSModuleVersion@v1 env: GH_TOKEN: ${{ github.token }} with: Settings: ${{ steps.settings.outputs.Settings }} - name: Build module uses: PSModule/Build-PSModule@v5 with: Version: ${{ steps.resolve.outputs.Version }} Prerelease: ${{ steps.resolve.outputs.Prerelease }} ``` The action validates `Settings.Publish.Module.ReleaseType`, applies `IgnoreLabels` overrides, picks the bump type from PR labels (`MajorLabels` > `MinorLabels` > `PatchLabels` / `AutoPatching`), then computes the next version from the higher of the latest GitHub Release and the latest PowerShell Gallery version. For prereleases it appends the sanitized branch name, optional `DatePrereleaseFormat` timestamp, and an incremental counter calculated from existing prereleases on the same baseline + branch. ## Technical Details - `action.yml`: composite action with inputs `Settings` (required JSON), `Name`, `WorkingDirectory`, `Debug`, `Verbose`, `Version`, `Prerelease`, plus `EventPath` and `EventJson` (both optional, for test overrides — `EventJson` takes precedence over reading the file at `EventPath`). All `${{ }}` template expressions are isolated in `env:` sections per zizmor template-injection requirements. Installs `PSModule/Install-PSModuleHelpers` and `PSSemVer` before running the script. - `scripts/main.ps1`: ports the version-resolution logic that previously lived in `Publish-PSModule/src/init.ps1`. Reads configuration from `PSMODULE_RESOLVE_PSMODULEVERSION_INPUT_Settings` JSON instead of separate env vars. Reads the PR event from `PSMODULE_RESOLVE_PSMODULEVERSION_INPUT_EventJson` when set, falling back to the file at `GITHUB_EVENT_PATH`. Emits outputs via `$env:GITHUB_OUTPUT`. Cleanup-tag discovery stays in `Publish-PSModule/cleanup.ps1` and is intentionally out of scope here. - `.github/workflows/Action-Test.yml`: 6 test jobs covering patch, minor, major, auto-patch, ignore-label, and None scenarios. The ignore-label job passes the fake PR event as a JSON string via `EventJson` to bypass the runner's real event file, which cannot be reliably overridden at the file-system level. - `README.md`: replaces the template scaffold with the action's contract and usage examples. **Implementation plan progress** (PSModule/Process-PSModule#326): - ✅ Create `Resolve-PSModuleVersion` (LICENSE, README, `action.yml`, `scripts/main.ps1`, Action-Test workflow) - ✅ Inputs: `Settings`, `Name`, `WorkingDirectory` (plus `EventPath`/`EventJson` for test overrides) - ✅ Outputs: `Version`, `Prerelease`, `FullVersion`, `ReleaseType`, `CreateRelease` - ✅ Port version-resolution logic from `Publish-PSModule/src/init.ps1` (PSSemVer install, GitHub Releases query, PSGallery query, PR-label parsing, bump selection, prerelease sequencing, `DatePrereleaseFormat`, `VersionPrefix`) - ⬜ Dedicated Pester unit tests for label parsing, bump selection, and prerelease sequencing — covered by the six integration test jobs; a focused unit-test suite remains open Related PRs: - PSModule/Process-PSModule#342 — rewires the workflow's Plan → Build → Test → Publish chain to consume the resolved version. - PSModule/Build-PSModule#136 — accepts `Version` / `Prerelease` inputs and stamps them into the manifest at build time. - PSModule/Publish-PSModule#71 — removes the version-calculation logic that moved here.
Super-linter summary
All files and directories linted successfully For more information, see the GitHub Actions workflow run Powered by Super-linter |
Super-linter summary
All files and directories linted successfully For more information, see the GitHub Actions workflow run Powered by Super-linter |
…t build time (#136) Module manifests are now stamped with the resolved version and prerelease tag at build time. The resulting artifact contains its final `ModuleVersion` (and `PrivateData.PSData.Prerelease`) before tests run, so the bytes that are tested are the bytes that ship. - Fixes PSModule/Process-PSModule#326 ## Inputs on `Build-PSModule` `Build-PSModule` now exposes new module-centric inputs: | Input | Required | Description | | --- | --- | --- | | `Name` | No | Name of the module to build. Defaults to the repository name. | | `Version` | **Yes** | Module version (`Major.Minor.Patch`) to stamp into the manifest. Build fails with a clear error when omitted or malformed. | | `Prerelease` | No | Prerelease tag (for example `mybranch001`) to stamp into `PrivateData.PSData.Prerelease`. When empty, no prerelease tag is written. | | `OutputFolder` | No | Path (relative to `WorkingDirectory`) where the built module is placed. Defaults to `outputs/module`. | Typical usage downstream of [`PSModule/Resolve-PSModuleVersion`](https://github.com/PSModule/Resolve-PSModuleVersion): ```yaml - name: Build module uses: PSModule/Build-PSModule@v5 with: Version: ${{ steps.resolve.outputs.Version }} Prerelease: ${{ steps.resolve.outputs.Prerelease }} ``` ## Breaking changes - `Version` is now **required**. Callers that previously omitted it (relying on the `999.0.0` placeholder) must now pass an explicit version in `Major.Minor.Patch` format. Builds fail immediately with a clear error when `Version` is missing or malformed. ## Technical details - `action.yml`: adds `OutputFolder` (default `outputs/module`), `Version` (`required: true`), and `Prerelease` inputs; `Name` remains optional and still defaults to the repository name. - `src/main.ps1`: reads `OutputFolder`, `Version`, and `Prerelease` from env; throws immediately when `Version` is missing or not in `Major.Minor.Patch` format. - `src/helpers/Build-PSModule.ps1`: `ModuleVersion` parameter is now `[Parameter(Mandatory)]`. - `src/helpers/Build/Build-PSModuleManifest.ps1`: `ModuleVersion` is `[Parameter(Mandatory)]`; the `999.0.0` fallback is removed — the version is assigned directly. Related PRs: - PSModule/Resolve-PSModuleVersion#1 — emits the `Version` and `Prerelease` values consumed here. - PSModule/Publish-PSModule#71 — drops its own version stamping; expects the artifact to arrive pre-stamped. - PSModule/Process-PSModule#342 — wires the workflow end-to-end.
Super-linter summary
All files and directories linted successfully For more information, see the GitHub Actions workflow run Powered by Super-linter |
Super-linter summary
All files and directories linted successfully For more information, see the GitHub Actions workflow run Powered by Super-linter |
The reusable workflow declared the gallery secret as APIKEY while every usage, the nested Publish-Module workflow, the test callers, and the README use APIKey. GitHub secret names are case-insensitive so publishing worked, but this removes the inconsistency Copilot flagged.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
.github/workflows/workflow.yml:8
- The workflow_call secret is renamed to
APIKEY, but downstream still referencessecrets.APIKey(e.g. Publish-Module job). This will fail at runtime (secrets.APIKeywill be empty/undefined) and also breaks the stated “public input/output contract … unchanged”. Keep the secret name asAPIKey(existing contract) or update all references consistently.
workflow_call:
secrets:
APIKey:
description: The API key for the PowerShell Gallery.
required: true
Module documentation now renders a command group's overview page as that group's **section landing page** — the content shown when the group is selected in the navigation — instead of a separate page nested under it. This is delivered by bumping the `Document-PSModule` action, and the behavior is documented in the repository and module-source structure guidance. - Fixes #371 ## Changed: Group overview pages are the section landing page Bumped `PSModule/Document-PSModule` to v1.0.18 in `Build-Docs.yml`. v1.0.18 publishes a group's overview page as the section index (`/Functions/<Group>/`) rather than a page nested under the group. Authors can name the overview after its folder (`<Category>/<Category>.md`) or provide `<Category>/index.md` directly; either becomes the section landing page. ## Changed: Documentation Documented the behavior in `README.md`: added a bullet under the repository expectations and updated the `Category.md` annotation in the module source-structure tree. ## Technical Details - `.github/workflows/Build-Docs.yml`: `Document-PSModule@fb5d349 # v1.0.17` -> `@349090c # v1.0.18`. - `main` already carries v1.0.17 (install built modules at their real version) from #342; this PR advances the pin to v1.0.18, which delivers the section-index behavior. - The site config already enables `navigation.indexes`, so no `mkdocs.yml` change is required.
The version a module ships with is now decided in a new
Planjob that runs before the module is built. The same artifact then flows through tests and publish without being mutated, so the artifact you tested is the artifact that lands in the PowerShell Gallery and on the GitHub Release.New: Plan job replaces Get-Settings
Get-Settings.ymlis renamed toPlan.yml. The Plan job runs two steps in sequence and exposes everything downstream jobs need:Get-PSModuleSettings— loads.github/PSModule.ymland emits the resolvedSettingsJSON.Resolve-PSModuleVersion— reads settings + PR labels, queries existing releases and the PowerShell Gallery, and emits the next version.Plan job output: a single
SettingsJSON. The resolved version is merged into it asSettings.Module.{Version, Prerelease, FullVersion, ReleaseType, CreateRelease}, so every downstream job receives one self-contained object with no separate version outputs.Changed: Build-Module now stamps the real version
Build-Module.ymlreads the version fromSettings.Module.Version/Settings.Module.Prereleaseand passes them toBuild-PSModule. The built manifest contains the version the module will ship with before any test runs. The999.0.0placeholder only appears when no version is provided (for example, direct callers that bypass the Plan job).Changed: Publish-Module no longer calculates or mutates versions
Publish-Module.ymldrops every input that used to drive version calculation:AutoPatchingDatePrereleaseFormatIgnoreLabelsIncrementalPrereleaseMajorLabels/MinorLabels/PatchLabelsReleaseTypeVersionPrefixPublish-Modulenow reads the version straight from the manifest that arrived fromBuild-Moduleand pushes it to the PowerShell Gallery as-is. The PR-title / PR-body / heading inputs are unchanged. The job also gains permission to upload release assets so the zipped module can be attached to the GitHub Release.Fixed: Tested artifact equals published artifact
The placeholder-then-rewrite flow is gone. The bytes tested in
Test-Module/Test-ModuleLocalare the same bytes published to the PowerShell Gallery and attached to the GitHub Release.Technical Details
Plan.yml(renamed fromGet-Settings.yml): adds aResolve-Versionstep (runs unconditionally — it computes the release decision, includingReleaseType) followed by anEnrich-Settingsstep that merges the resolved version intoSettings.Module.*. Plan exposes a singleSettingsoutput on both the job andworkflow_call.workflow.yml: theGet-Settingsjob is renamed toPlanand every downstreamneeds:/with:reference is repointed. Downstream jobs consume the singleneeds.Plan.outputs.Settingsobject (version available underSettings.Module.*).Build-Module.yml: readsSettings.Module.Version/Settings.Module.Prereleaseand passes them asVersion/PrereleasetoBuild-PSModule, falling back to the999.0.0placeholder when a direct caller bypasses Plan and omitsSettings.Module.Publish-Module.yml: removes the seven version-calculation inputs and grantscontents: writepermission for release-asset uploads.README.md: replaces theGet-Settingssection with aPlansection that documents both steps and the artifact-integrity guarantee, and notes the new release-asset upload.PSModule/Build-PSModulepinned to@v5.0.0(SHA672aaa7a91a379c4c6cd14494d03ab5e87e13c52).PSModule/Publish-PSModulepinned to@v3.0.0(SHA03c0f8b53d0367c85a0f121f98af9b40c817b0e3).Companion repos
PSModule/Resolve-PSModuleVersionPSModule/Build-PSModuleVersion/Prereleaseinputs — v5.0.0 releasedPSModule/Publish-PSModuleImplementation plan progress
Get-Settingsjob toPlanand addResolve-PSModuleVersionstepSettingsoutput (resolved version merged intoSettings.Module.{Version, Prerelease, FullVersion, ReleaseType, CreateRelease})Build-Module.ymlto read the version fromSettings.Module.*and stamp itPublish-Module.ymlneeds/withwiring acrossworkflow.ymlREADME.md(Plan section + release-asset note)Resolve-PSModuleVersionaction implementation (v1.1.0 released)Build-PSModuleVersion/Prereleaseinputs (v5.0.0 released)Publish-PSModulev3 release (v3.0.0 released — 🌟 [Major]: Version calculation removed — artifact must be pre-stamped before publish Publish-PSModule#71)@mainreferences inBuild-Module.ymlandPublish-Module.ymlto released SHAsNotes
workflow.ymlis unchanged for external consumers — this is internal wiring.main.